﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Seagull.Configuration;
using Seagull.Events;
using Seagull.Events.Sms;
using Seagull.Generic;
using Seagull.Model.Alerts;
using Seagull.Model.Sms;

namespace Seagull.Alerts
{
    /// <summary>
    ///     报警管理器。启动后监听报警。当有报警发生时，发送短信并记录。
    /// </summary>
    public class AlertManager : IModule
    {
        #region Singleton

        private static readonly Lazy<AlertManager> _instance =
            new Lazy<AlertManager>(() => new AlertManager());

        public static AlertManager Instance
        {
            get { return _instance.Value; }
        }

        #endregion

        private RegistrationToken _alertEventToken;
        private RegistrationToken _logOnlyAlertEventToken;
        private Thread _logAlertThread;
        private volatile bool _hasStopped;
        private readonly object _syncRoot = new object();

        private AlertManager()
        {
        }

        /// <summary>
        ///     开始监听报警。当有报警发生时，发送短信并记录。若短信发送失败，则定期重新发送。
        /// </summary>
        public void Start()
        {
            if(_logAlertThread != null)
            {
                throw new InvalidOperationException("The AlertManager is already started.");
            }
            _keepRunning = true;
            _hasStopped = false;
            _logAlertThread = new Thread(LogAlertThreadProc) { Name = "LogAlertThread" };
            _logAlertThread.Start();

            _alertEventToken = AlertEvent.Instance.Register(OnAlert);
            _logOnlyAlertEventToken = LogOnlyAlertEvent.Instance.Register(LogAlert);
        }

        public void Stop()
        {
            lock(_syncRoot)
            {
                if(!_hasStopped)
                {
                    AlertEvent.Instance.UnRegister(_alertEventToken);
                    LogOnlyAlertEvent.Instance.UnRegister(_logOnlyAlertEventToken);

                    _keepRunning = false;
                    if(_logAlertThread != null && _logAlertThread.IsAlive && !_logAlertThread.Join(ThreadSleepIntervalOnError))
                    {
                        _logAlertThread.Abort();
                    }
                    _logAlertThread = null;

                    if(!_alertQueue.IsEmpty)
                    {
                        WriteAlerts(_alertQueue);
                    }
                    _hasStopped = true;
                }
            }
        }

        internal void OnAlert(Alert alert)
        {
            // TODO: Change to use alert queue.
            // TODO: Add tests.
            LogAlert(alert);

            if(Config.Instance.SmsAlertConfig.AlarmSmsEnabled)
            {
                SendAlert(alert);
            }
        }

        #region Log Alert Into File

        private const int ThreadSleepIntervalOnError = 4000;
        private readonly ConcurrentQueue<Alert> _alertQueue = new ConcurrentQueue<Alert>();
        private bool _keepRunning;

        private void LogAlertThreadProc()
        {
            const int sleepInterval = 2000;
            while(_keepRunning)
            {
                try
                {
                    Alert alert;
                    if(_alertQueue.TryDequeue(out alert))
                    {
                        WriteAlert(alert);
                    }
                    else
                    {
                        Thread.Sleep(sleepInterval);
                    }
                }
                catch(ThreadAbortException)
                {
                    // Actually this is unnecessary because the ThreadAbortException will be rethrown at the end of catch block.
                    break;
                }
                catch(Exception exception)
                {
                    NotificationEvent.Instance.Publish(
                           string.Format("写入报警信息文件发生错误：{0}", exception.GetType().Name));
                    Thread.Sleep(ThreadSleepIntervalOnError);
                }
            }
        }

        private static void LogAlert(Alert alert)
        {
            var logAlertTask = Task.Factory.StartNew(() => WriteAlert(alert));

            logAlertTask.ContinueWith(t =>
            {
                try
                {
                    var exception = t.Exception.InnerException;
                    NotificationEvent.Instance.Publish(
                        string.Format("写入报警信息文件发生错误：{0}", exception.GetType().Name));
                }
                catch
                {
                    // Ingore.
                }
            }, TaskContinuationOptions.OnlyOnFaulted | TaskContinuationOptions.ExecuteSynchronously);
        }

        private static void WriteAlert(Alert alert)
        {
            WriteAlerts(new[] { alert });
        }

        private static void WriteAlerts(IEnumerable<Alert> alertsToWrite)
        {
            HistoryAlerts.Change(alerts => alerts.Alerts.AddRange(alertsToWrite.Select(alert => alert.ToGenericAlert())));
        }

        #endregion

        #region Send Alert via SMS

        private const int MaxDailyMessageCountPerAlertType = 2;

        /// <summary>
        /// 储存一天的所有报警。Key为报警内容，Value为<see cref="AlertLog"/>的列表。
        /// </summary>
        private readonly Dictionary<string, List<AlertLog>> _alertCollection = 
            new Dictionary<string, List<AlertLog>>();

        private readonly object _alertCollectionSyncRoot = new object();

        /// <summary>
        ///     发送报警短信的逻辑： 不一定每次都发送  <para />
        /// 记录所有的报警及其时间、是否发送了短信 <para />
        /// 一小时内发送过同内容的短信则本次不发送 <para />
        /// 一天内发送过两条同内容的短信则本次不发送 <para />
        /// 若是每日例行报警，则替换为最近一小时以内的报警集合，然后清空报警集合<para />
        /// </summary>
        /// <param name="alert"> </param>
        private void SendAlert(Alert alert)
        {
            try
            {
                var alertToSend = DetermineAlertToSend(alert);
                if(alertToSend != null)
                {
                    var alertMessage = new SmsMessage(alertToSend.ToString(),
                        Config.Instance.SmsAlertConfig.SmsRecipients);
                    SendSmsMessageEvent.Instance.Publish(alertMessage);
                }
            }
            catch(Exception exception)
            {
                LogOnlyAlertEvent.Instance.Publish(new ApplicationAlert(exception, "准备报警短信"));
            }
        }

        private Alert DetermineAlertToSend(Alert alert)
        {
            lock(_alertCollectionSyncRoot)
            {
                if(alert is DailyAlert)
                {
                    return DetermineAlertToSendForDailyAlert(alert);
                }
                else
                {
                    return DetermineAlertToSendForNormalAlert(alert);
                }
            }
        }

        private Alert DetermineAlertToSendForNormalAlert(Alert alert)
        {
            if(!_alertCollection.ContainsKey(alert.AlertContent))
            {
                _alertCollection[alert.AlertContent] = new List<AlertLog>();
            }
            // 取出该类报警记录的列表
            var alerts = _alertCollection[alert.AlertContent];
            if(ShouldSupressAlertMessage(alerts))
            {
                alerts.Add(new AlertLog(alert, false));
                return null;
            }
            else
            {
                alerts.Add(new AlertLog(alert, true));
                return alert;
            }
        }

        private Alert DetermineAlertToSendForDailyAlert(Alert alert)
        {
            var aggregatedAlertContents = _alertCollection
                .Where(alertLogs => alertLogs.Value.Any(l => l.Alert.Time > DateTime.Now.AddHours(-1)))
                .Select(t => t.Key).ToArray();
            _alertCollection.Clear();
            if(aggregatedAlertContents.Length > 0)
            {
                var aggregatedAlert = new GenericAlert { AlertContent = "报警尚未解除: " + string.Join(";", aggregatedAlertContents) };
                return aggregatedAlert;
            }
            return alert;
        }

        /// <summary>
        /// 检查此类报警是否在短时间内已经太多。
        /// </summary>
        /// <param name="alerts"></param>
        /// <returns></returns>
        private static bool ShouldSupressAlertMessage(List<AlertLog> alerts)
        {
            return
                alerts.Any(t => t.SmsSent && t.Alert.Time > DateTime.Now.AddHours(-1)) ||
                    alerts.Count(t => t.SmsSent) >= MaxDailyMessageCountPerAlertType;
        }

        #endregion

        #region Nested type: AlertLog

        /// <summary>
        /// 代表一条报警记录。存储了<see cref="Alert"/>实例并标明其是否曾通过短信发送。
        /// </summary>
        [DebuggerDisplay("{ToString()}")]
        private struct AlertLog
        {
            public AlertLog(Alert alert, bool smsSent)
            {
                _alert = alert;
                _smsSent = smsSent;
            }

            private readonly Alert _alert;
            /// <summary>
            /// 报警<see cref="Alert"/>实例。
            /// </summary>
            public Alert Alert
            {
                get { return _alert; }
            }

            private readonly bool _smsSent;
            /// <summary>
            /// 此报警是否曾通过短信发送。
            /// </summary>
            public bool SmsSent
            {
                get { return _smsSent; }
            }

            public override string ToString()
            {
                return string.Format("Alert: {0}, SmsSent: {1}", _alert, _smsSent);
            }
        }

        #endregion
    }
}